package com.hsjk.szwj.module.pay.service;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.v3.auth.AutoUpdateCertificatesVerifier;
import com.github.binarywang.wxpay.v3.auth.PrivateKeySigner;
import com.github.binarywang.wxpay.v3.auth.WxPayCredentials;
import com.github.binarywang.wxpay.v3.util.PemUtils;
import com.hsjk.szwj.framework.common.exception.ServiceException;
import com.hsjk.szwj.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.hsjk.szwj.module.pay.dal.dataobject.config.ChannelRetMsg;
import com.hsjk.szwj.module.pay.dal.dataobject.config.MchAppConfigContext;
import com.hsjk.szwj.module.pay.dal.dataobject.config.WxServiceWrapper;
import com.hsjk.szwj.module.pay.dal.dataobject.order.PayOrderDO;
import com.hsjk.szwj.module.pay.dal.dataobject.system.MchAppDO;
import com.hsjk.szwj.module.pay.dal.dataobject.system.MchInfoDO;
import com.hsjk.szwj.module.pay.enums.IfCodeEnum;
import com.hsjk.szwj.module.pay.enums.WxPayVersionEnum;
import com.hsjk.szwj.module.pay.exception.ResponseException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.FileInputStream;
import java.math.BigDecimal;
import java.security.PrivateKey;

/**
 * @author chenjiawei
 * @desc 微信回调
 * @Date 2023/08/24 11:34
 **/
@Service
@Slf4j
public class WxpayChannelNoticeService extends AbstractChannelNoticeService {
    @Resource
    private ConfigContextQueryService configContextQueryService;

    @Resource
    private PayOrderService payOrderService;
    @Resource
    private IMchInfoService mchInfoService;
    @Resource
    private IMchAppService mchAppService;

    @Override
    public String getIfCode() {
        return IfCodeEnum.WXPAY.getCode();
    }

    @Override
    public MutablePair<Long, Object> parseParams(HttpServletRequest request,
                                                 Long orderId,
                                                 NoticeTypeEnum noticeTypeEnum) {
        log.info("parseParams:{}", orderId);
        try {
            // V3接口回调
            if (ObjectUtil.isNotNull(orderId)) {

                // 获取订单信息
                PayOrderDO payOrder = payOrderService.getById(orderId);
                if (payOrder == null) {
                    throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
                            "订单不存在");
                }
                MchInfoDO mchInfoDO = mchInfoService.getMchInfoById(payOrder.getMchId());
                MchAppDO mchAppDO = mchAppService.getById(payOrder.getAppId());
                //获取支付参数 (缓存数据) 和 商户信息
                MchAppConfigContext mchAppConfigContext =
                        configContextQueryService.queryMchInfoAndAppInfo(mchInfoDO.getId(),
                                mchAppDO.getId());
                if (mchAppConfigContext == null) {
                    throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
                            "获取商户信息失败");
                }

                // 验签 && 获取订单回调数据
                WxPayOrderNotifyV3Result.DecryptNotifyResult result =
                        parseOrderNotifyV3Result(request, mchAppConfigContext);

                return MutablePair.of(Long.valueOf(result.getOutTradeNo()), result);

            } else {
                // V2接口回调
                String xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
                if (StringUtils.isEmpty(xmlResult)) {
                    return null;
                }

                WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xmlResult);
                String payOrderId = result.getOutTradeNo();
                return MutablePair.of(Long.valueOf(payOrderId), result);
            }
        } catch (Exception e) {
            log.error("error", e);
            throw ResponseException.buildText("ERROR");
        }
    }

    @Override
    public ChannelRetMsg doNotice(HttpServletRequest request, Object params,
                                  PayOrderDO payOrder, MchAppConfigContext mchAppConfigContext, NoticeTypeEnum noticeTypeEnum) {
        log.info("doNotice:params-{}", JSONUtil.parseObj(params));
        try {
            ChannelRetMsg channelResult = new ChannelRetMsg();
            // 默认支付中
            channelResult.setChannelState(ChannelRetMsg.ChannelState.WAITING);

            WxServiceWrapper wxServiceWrapper = configContextQueryService.getWxServiceWrapper(mchAppConfigContext);

            if (WxPayVersionEnum.WX_V2.getVersion()
                    .equals(wxServiceWrapper.getApiVersion())) {
                // V2
                // 获取回调参数
                WxPayOrderNotifyResult result = (WxPayOrderNotifyResult) params;

                WxPayService wxPayService = wxServiceWrapper.getWxPayService();

                // 验证参数
                verifyWxPayParams(wxPayService, result, payOrder);
                //渠道订单号
                channelResult.setChannelOrderId(result.getTransactionId());
                //支付用户ID
                channelResult.setChannelUserId(result.getOpenid());
                channelResult.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS);
                channelResult.setResponseEntity(textResp(WxPayNotifyResponse.successResp("OK")));

            } else if (WxPayVersionEnum.WX_V3.getVersion()
                    .equals(wxServiceWrapper.getApiVersion())) {
                // V3
                // 获取回调参数
                WxPayOrderNotifyV3Result.DecryptNotifyResult result = (WxPayOrderNotifyV3Result.DecryptNotifyResult) params;

                // 验证参数
                verifyWxPayParams(result, payOrder);

                String channelState = result.getTradeState();
                if ("SUCCESS".equals(channelState)) {
                    channelResult.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS);
                } else if ("CLOSED".equals(channelState)
                        || "REVOKED".equals(channelState)
                        || "PAYERROR".equals(channelState)) {
                    //CLOSED—已关闭， REVOKED—已撤销, PAYERROR--支付失败
                    //支付失败
                    channelResult.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_FAIL);
                }
                //渠道订单号
                channelResult.setChannelOrderId(result.getTransactionId());
                WxPayOrderNotifyV3Result.Payer payer = result.getPayer();
                if (payer != null) {
                    //支付用户ID
                    channelResult.setChannelUserId(payer.getOpenid());
                }

                JSONObject resJSON = new JSONObject();
                resJSON.put("code", "SUCCESS");
                resJSON.put("message", "成功");

                ResponseEntity okResponse = jsonResp(resJSON);
                //响应数据
                channelResult.setResponseEntity(okResponse);

            } else {
                throw ResponseException.buildText("API_VERSION ERROR");
            }

            return channelResult;

        } catch (Exception e) {
            log.error("error", e);
            throw ResponseException.buildText("ERROR");
        }
    }

    /**
     * V2接口验证微信支付通知参数
     *
     * @return
     */
    public void verifyWxPayParams(WxPayService wxPayService, WxPayOrderNotifyResult result, PayOrderDO payOrder) {

        try {
            result.checkResult(wxPayService, WxPayConstants.SignType.MD5, true);

            // 核对金额// 总金额
            Integer total_fee = result.getTotalFee();
            long wxPayAmt = new BigDecimal(total_fee).longValue();
            long dbPayAmt = payOrder.getAmount().longValue();
            if (dbPayAmt != wxPayAmt) {
                throw ResponseException.buildText("AMOUNT ERROR");
            }
        } catch (Exception e) {
            throw ResponseException.buildText("ERROR");
        }
    }

    /**
     * V3校验通知签名
     *
     * @param request             请求信息
     * @param mchAppConfigContext 商户配置
     * @return true:校验通过 false:校验不通过
     */
    private WxPayOrderNotifyV3Result.DecryptNotifyResult parseOrderNotifyV3Result(HttpServletRequest request,
                                                                                  MchAppConfigContext mchAppConfigContext) throws Exception {
        log.info("parseOrderNotifyV3Result");
        SignatureHeader header = new SignatureHeader();
        header.setTimeStamp(request.getHeader("Wechatpay-Timestamp"));
        header.setNonce(request.getHeader("Wechatpay-Nonce"));
        header.setSerial(request.getHeader("Wechatpay-Serial"));
        header.setSignature(request.getHeader("Wechatpay-Signature"));

        // 获取加密信息
        String params = getReqParamFromBody();

        log.info("【请求头信息】：{}\n【加密数据】：{}", header, params);

        WxPayService wxPayService = configContextQueryService.getWxServiceWrapper(mchAppConfigContext).getWxPayService();
        WxPayConfig wxPayConfig = wxPayService.getConfig();
        // 自动获取微信平台证书
        PrivateKey privateKey = PemUtils.loadPrivateKey(new FileInputStream(wxPayConfig.getPrivateKeyPath()));
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WxPayCredentials(wxPayConfig.getMchId(), new PrivateKeySigner(wxPayConfig.getCertSerialNo(), privateKey)),
                wxPayConfig.getApiV3Key().getBytes("utf-8"));
        wxPayConfig.setVerifier(verifier);
        wxPayService.setConfig(wxPayConfig);

        WxPayOrderNotifyV3Result result = wxPayService.parseOrderNotifyV3Result(params, header);

        return result.getResult();
    }

    /**
     * V3接口验证微信支付通知参数
     *
     * @return
     */
    public void verifyWxPayParams(WxPayOrderNotifyV3Result.DecryptNotifyResult result,
                                  PayOrderDO payOrder) {

        try {
            // 核对金额// 总金额
            Integer total_fee = result.getAmount().getTotal();
            long wxPayAmt = new BigDecimal(total_fee).longValue();
            long dbPayAmt = payOrder.getAmount().longValue();
            if (dbPayAmt != wxPayAmt) {
                throw ResponseException.buildText("AMOUNT ERROR");
            }
        } catch (Exception e) {
            throw ResponseException.buildText("ERROR");
        }
    }
}
